package za.org.foobar.famon.appengine.migration;

import com.google.appengine.api.datastore.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import za.org.foobar.famon.model.ApplicationGroup;
import za.org.foobar.famon.model.Failure;
import za.org.foobar.famon.model.SourceLocation;
import za.org.foobar.famon.util.DB;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;

/**
 *
 */
public class DataMigrationServlet extends HttpServlet {
    private final Log log = LogFactory.getLog(getClass());

    private List<DataMigration> dataMigration = new ArrayList<DataMigration>();

    public DataMigrationServlet() {
        log.debug("Creating DataMigrationServlet");
        addDataMigration(new DataMigrationAddField(ApplicationGroup.class, "version", 1));
        addDataMigration(new DataMigrationAddField(ApplicationGroup.class, "failureCount", 1));
        addDataMigration(new DataMigrationAddField(Failure.class, "version", 1));
        addDataMigration(new DataMigrationAddField(SourceLocation.class, "version", 1));
    }

    public void addDataMigration(DataMigration m) {
        dataMigration.add(m);
    }

    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        log.info("Processing request: " + request.getQueryString());
        PrintWriter out = response.getWriter();
        out.println("<html><body><pre>");
        out.println("Migrating Data:");
        String className = request.getParameter("className");
        String entryStr = request.getParameter("entry");
        String migrationStr = request.getParameter("migration");
        long entry = entryStr != null ? Long.parseLong(entryStr) :-1;
        long migration = migrationStr != null ? Long.parseLong(migrationStr) :-1;

        out.println("Starting at " + className + ", " + entry + ", " + migration);

        String lastClassName = null;
        long lastEntry = 0;
        long lastMigration = 0;
        long threshold = 2000;
        long start = System.currentTimeMillis();
        try {
            DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
            Map<Class, List<DataMigration>> map = getMap(dataMigration);
            ArrayList<Class> classes = new ArrayList<Class>(map.keySet());
            Collections.sort(classes, new Comparator<Class>() {
                public int compare(Class o1, Class o2) {
                    return o1.getName().compareTo(o2.getName());
                }
            });

            for (Class cls : classes) {
                lastClassName = cls.getSimpleName();
                Query q = new Query(cls.getSimpleName());
                if (className != null) {
                    if (className.equals(lastClassName)) {
                        className = null;
                    } else {
                        continue;
                    }
                }
                out.println("Migrating using Query: " + q.toString());
                PreparedQuery preparedQuery = datastore.prepare(q);
                lastEntry = 0;
                for (Entity e : preparedQuery.asIterable()) {
                    if (entry >= 0) {
                        if (entry == lastEntry) {
                            entry = -1;
                        } else {
                            lastEntry++;
                            continue;
                        }
                    }
                    lastMigration = 0;
                    for (DataMigration m : map.get(cls)) {
                        if (migration >= 0) {
                            if (migration == lastMigration) {
                                migration = -1;
                            } else {
                                lastMigration++;
                                continue;
                            }
                        }
                        if (m.migrate(e, out)) {
                            datastore.put(e);
                        }
                        lastMigration++;
                        long t = System.currentTimeMillis() - start;
                        if (t >= threshold) {
                            throw new TimeoutException("Forced timeout after " + t + " ms");
                        }
                        // TODO: remove this
                        Thread.sleep(100);
                    }
                    lastEntry++;
                }
                out.println("Migrated " + lastEntry + " entries");
            }
            lastClassName = null;

        } catch (TimeoutException e) {
            out.println(e.getMessage());
        } catch (Throwable e) {
            e.printStackTrace(out);
        } finally {
            DB.closePM();
            out.println("Migrated " + lastEntry + " entries");
            if (lastClassName != null) {
                out.println("Class = " + lastClassName);
                out.println("Entry = " + lastEntry);
                out.println("Migration = " + lastMigration);
                out.println("</pre>");
                out.println("<iframe src=\"" + request.getRequestURI() + "?className="
                        + lastClassName + "&entry=" + lastEntry + "&migration=" + lastMigration
                        + "\" width=\"100%\" height=\"600px\"></iframe>");
            } else {
                out.println("</pre>");
            }
            out.println("</body></html>");
            out.close();
        }
    }

    public Map<Class, List<DataMigration>> getMap(List<DataMigration> dataMigration) {
        Map<Class, List<DataMigration>> map = new HashMap<Class, List<DataMigration>>();
        for (DataMigration m : dataMigration) {
            Class cls = m.getEntityClass();
            List<DataMigration> list = map.get(cls);
            if (list == null) {
                list = new ArrayList<DataMigration>();
                map.put(cls, list);
            }
            list.add(m);
        }
        return map;
    }

    public class TimeoutException  extends Exception {
        public TimeoutException(String message) {
            super(message);
        }
    }
}
